﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace StorageSystem.HT
{
    /// <summary>
    /// Read / Write File Stream, Optimizaed for HsahTable
    /// </summary>
    internal sealed class StreamRW : FileStream
    {

        private Encoding mEncoding;
        private string mfileName;
        private byte[] mBuffer;

        /*//////////////////////////////////////////////////////////////////////////////////////////*/

        public StreamRW(string fileName, FileMode mode, FileAccess access, FileShare share, int bufferSize)
            : base(fileName, mode, access, share, bufferSize)
        {
            mEncoding = Encoding.Unicode;
            mfileName = fileName;
            mBuffer = new byte[16];
        }

        public StreamRW(string fileName, FileMode mode, FileAccess access, FileShare share, int bufferSize, Encoding encoding)
            : base(fileName, mode, access, share, bufferSize)
        {
            mEncoding = encoding;
            mfileName = fileName;
            mBuffer = new byte[16];
        }

        /*//////////////////////////////////////////////////////////////////////////////////////////*/

        #region Write methods

        internal void WriteFileHeader(FileHeader info)
        {
            Seek(0, SeekOrigin.Begin);
            WriteInt32(info.Count);
            WriteInt32(info.Capacity);
            WriteInt32(info.MaxRound);
        }

        public void WriteInt64(long value)
        {
            mBuffer[0] = (byte)value;
            mBuffer[1] = (byte)(value >> 8);
            mBuffer[2] = (byte)(value >> 0x10);
            mBuffer[3] = (byte)(value >> 0x18);
            mBuffer[4] = (byte)(value >> 0x20);
            mBuffer[5] = (byte)(value >> 40);
            mBuffer[6] = (byte)(value >> 0x30);
            mBuffer[7] = (byte)(value >> 0x38);

            Write(mBuffer, 0, 8);
        }

        public void WriteInt32(int value)
        {
            mBuffer[0] = (byte)value;
            mBuffer[1] = (byte)(value >> 8);
            mBuffer[2] = (byte)(value >> 16);
            mBuffer[3] = (byte)(value >> 24);

            Write(mBuffer, 0, 4);
        }

        public void WriteUint32(uint value)
        {
            mBuffer[0] = (byte)value;
            mBuffer[1] = (byte)(value >> 8);
            mBuffer[2] = (byte)(value >> 16);
            mBuffer[3] = (byte)(value >> 24);
            Write(mBuffer, 0, 4);
        }

 


        public void WriteString(string value)
        {
            var bytes = mEncoding.GetBytes(value);
            Write(bytes, 0, bytes.Length);
        }

        /*///////////////////////////////////////////////////////////////////////////*/

        public void AppendNewItem(StringEntry item)
        {
            Seek(0, SeekOrigin.End);

            var KeyBytes = mEncoding.GetBytes(item.Key);
            var ValueBytes = mEncoding.GetBytes(item.Value);

            int ItemLength = KeyBytes.Length + ValueBytes.Length;
            SetLength(Length + 16 + ItemLength);

            WriteInt32(ItemLength);
            WriteInt32(KeyBytes.Length);
            WriteInt64(item.NextLocation);
            Write(KeyBytes, 0, KeyBytes.Length);
            Write(ValueBytes, 0, ValueBytes.Length);
        }

        public void AppendNewItem(BinaryEntry item)
        {
            Seek(0, SeekOrigin.End);

            var KeyBytes = mEncoding.GetBytes(item.Key);

            int ItemLength = KeyBytes.Length + item.Value.Length;
            SetLength(Length + 16 + ItemLength);

            WriteInt32(ItemLength);
            WriteInt32(KeyBytes.Length);
            WriteInt64(item.NextLocation);
            Write(KeyBytes, 0, KeyBytes.Length);
            Write(item.Value, 0, item.Value.Length);
        }

        public void AppendNewItem(string key, string value)
        {
            Seek(0, SeekOrigin.End);

            var KeyBytes = mEncoding.GetBytes(key);
            var ValueBytes = mEncoding.GetBytes(value);

            int ItemLength = KeyBytes.Length + ValueBytes.Length;
            SetLength(Length + 16 + ItemLength);

            WriteInt32(ItemLength);
            WriteInt32(KeyBytes.Length);
            WriteInt64(-1);
            Write(KeyBytes, 0, KeyBytes.Length);
            Write(ValueBytes, 0, ValueBytes.Length);
        }

        public void AppendNewItem(string key, byte[] value)
        {
            Seek(0, SeekOrigin.End);

            var KeyBytes = mEncoding.GetBytes(key);

            int ItemLength = KeyBytes.Length + value.Length;
            SetLength(Length + 16 + ItemLength);

            WriteInt32(ItemLength);
            WriteInt32(KeyBytes.Length);
            WriteInt64(-1);
            Write(KeyBytes, 0, KeyBytes.Length);
            Write(value, 0, value.Length);
        }

        public void UpdateNextLocationField(long location)
        {
            Seek(location, SeekOrigin.Begin);
            WriteInt64(Length);
        }

        #endregion

        /*//////////////////////////////////////////////////////////////////////////////////////////*/

        #region Read method

        internal FileHeader ReadFileHeader()
        {
            byte[] data = new byte[12];
            FileHeader R = new FileHeader();

            Read(data, 0, 12);
            R.Count = ReadInt32(data, 0);
            R.Capacity = ReadInt32(data, 4);
            R.MaxRound = ReadInt32(data, 8);

            return R;
        }

        internal StringEntry ReadStringItem(long pos)
        {
            if (pos < 0 || pos > Length) return null;

            Seek(pos, SeekOrigin.Begin);
            Read(mBuffer, 0, 16);

            int realSize = ReadInt32(mBuffer, 0);
            if (realSize == 0) return null;
            
            StringEntry R = new StringEntry();
            int keySize = ReadInt32(mBuffer, 4);

            R.NextLocation = ReadInt64(mBuffer, 8);
            R.Location = pos;

            byte[] Data = new byte[realSize];
            Read(Data, 0, realSize);
            R.Key = ReadString(Data, 0, keySize);
            R.Value = ReadString(Data, keySize, realSize - keySize);

            return R;
        }

        internal BinaryEntry ReadBinaryItem(long pos)
        {
            if (pos < 0 || pos > Length) return null;

            Seek(pos, SeekOrigin.Begin);
            Read(mBuffer, 0, 16);

            int realSize = ReadInt32(mBuffer, 0);
            if (realSize == 0) return null;

            BinaryEntry R = new BinaryEntry();
            int keySize = ReadInt32(mBuffer, 4);

            R.NextLocation = ReadInt64(mBuffer, 8);
            R.Location = pos;

            byte[] Data = new byte[realSize];
            Read(Data, 0, realSize);
            R.Key = ReadString(Data, 0, keySize);
            R.Value = new byte[realSize - keySize]; 
            Data.CopyTo(R.Value, keySize);

            return R;
        }


        internal StringEntry SecureReadStringItem(long pos, string key)
        {
            StringEntry R = new StringEntry();
            if (pos < 0 || pos > Length) return null;

            try
            {
            restart:
                Seek(pos, SeekOrigin.Begin);
                Read(mBuffer, 0, 16);

                int realSize = ReadInt32(mBuffer, 0);
                if (realSize == 0) return null;
                int keySize = ReadInt32(mBuffer, 4);

                byte[] Data = new byte[realSize];
                Read(Data, 0, keySize);
                R.Key = ReadString(Data, 0, keySize);

                if (!string.Equals(R.Key, key, StringComparison.Ordinal))
                {
                    pos = ReadInt64(mBuffer, 8);
                    if (pos == -1 || pos < 0 || pos > Length)
                        return null;
                    else
                        goto restart;
                }

                Read(Data, keySize, realSize - keySize);

                R.NextLocation = ReadInt64(mBuffer, 8);
                R.Location = pos;
                R.Value = ReadString(Data, keySize, realSize - keySize);
                return R;
            }
            catch { return null; }
        }

        internal BinaryEntry SecureReadBinaryItem(long pos, string key)
        {
            BinaryEntry R = new BinaryEntry();
            if (pos < 0 || pos > Length) return null;

            try
            {
            restart:
                Seek(pos, SeekOrigin.Begin);
                Read(mBuffer, 0, 16);

                int realSize = ReadInt32(mBuffer, 0);
                if (realSize == 0) return null;
                int keySize = ReadInt32(mBuffer, 4);

                byte[] Data = new byte[realSize];
                Read(Data, 0, keySize);
                R.Key = ReadString(Data, 0, keySize);

                if (!string.Equals(R.Key, key, StringComparison.Ordinal))
                {
                    pos = ReadInt64(mBuffer, 8);
                    if (pos == -1 || pos < 0 || pos > Length)
                        return null;
                    else
                        goto restart;
                }

                Read(Data, keySize, realSize - keySize);

                R.NextLocation = ReadInt64(mBuffer, 8);
                R.Location = pos;
                R.Value = new byte[realSize - keySize];
                Data.CopyTo(R.Value, keySize);
                return R;
            }
            catch { return null; }
        }

        internal bool GoToLastItemUnique(long pos, string key, out long lastItemLocation)
        {
            lastItemLocation = -1;

            restart:
                if (pos < 0 || pos > Length) return false;
                Seek(pos, SeekOrigin.Begin);
                Read(mBuffer, 0, 16);

                int realSize = ReadInt32(mBuffer, 0);
                if (realSize == 0) return false;
                int keySize = ReadInt32(mBuffer, 4);

                byte[] Data = new byte[keySize];
                Read(Data, 0, keySize);
                var Key = ReadString(Data, 0, keySize);

                if (string.Equals(Key, key, StringComparison.Ordinal)) return false;

                long nextLocation = ReadInt64(mBuffer, 8);
                if (nextLocation == -1)
                {
                    lastItemLocation = pos;
                    return true;
                }
                else
                {
                    pos = nextLocation;
                    goto restart;
                }
            
        }

        /*///////////////////////////////////////////////////////////////////////////*/

        public static int ReadInt32(byte[] bytes)
        {
            return (((bytes[0] | (bytes[1] << 8)) | (bytes[2] << 0x10)) | (bytes[3] << 0x18));
        }

        public static int ReadInt32(byte[] bytes, int startIndex)
        {
            return (((bytes[startIndex] | (bytes[startIndex + 1] << 8)) | (bytes[startIndex + 2] << 0x10)) | (bytes[startIndex + 3] << 0x18));
        }

        public static uint ReadUInt32(byte[] bytes)
        {
            return (uint)(((bytes[0] | (bytes[1] << 8)) | (bytes[2] << 0x10)) | (bytes[3] << 0x18));
        }

        public static uint ReadUInt32(byte[] bytes, int startIndex)
        {
            return (uint)(((bytes[startIndex] | (bytes[startIndex + 1] << 8)) | (bytes[startIndex + 2] << 0x10)) | (bytes[startIndex + 3] << 0x18));
        }

        public static long ReadInt64(byte[] bytes)
        {
            long num = (uint)(((bytes[0] | (bytes[1] << 8)) | (bytes[2] << 0x10)) | (bytes[3] << 0x18));
            long num2 = (uint)(((bytes[4] | (bytes[5] << 8)) | (bytes[6] << 0x10)) | (bytes[7] << 0x18));
            return (long)((num2 << 0x20) | num);
        }

        public static long ReadInt64(byte[] bytes, int startIndex)
        {
            long num = (uint)(((bytes[startIndex] | (bytes[startIndex + 1] << 8)) | (bytes[startIndex + 2] << 0x10)) | (bytes[startIndex + 3] << 0x18));
            long num2 = (uint)(((bytes[startIndex + 4] | (bytes[startIndex + 5] << 8)) | (bytes[startIndex + 6] << 0x10)) | (bytes[startIndex + 7] << 0x18));
            return (long)((num2 << 0x20) | num);
        }

        string ReadString(byte[] bytes)
        {
            return mEncoding.GetString(bytes);
        }

        string ReadString(byte[] bytes, int index, int count)
        {
            return mEncoding.GetString(bytes, index, count);
        }

        #endregion
    }
}


